﻿/*****************************************************************************\
 *             University of Illinois/NCSA Open Source License
 *
 * 
 *      Copyright © 2012,2013, The Board of Trustees of the University of Illinois. All rights reserved.
 *
 *      Developed by: Rakesh Bobba, Derek Dagit, Erich Heine, Denis Kholine,
 *                    Klara Nahrstedt, David Rodgers,   William H. Sanders, Tim Yardley
 *
 *      Information Trust Institute/Coordinated Science Laboratory
 * 
 *      University of Illinois
 *      
 *      The Board of Trustees of the University of Illinois
 *      109 Coble Hall
 *      801 South Wright Street
 *      Champaign, IL 61820-6242
 *
 *      http://www.iti.illinois.edu / http://csl.illinois.edu
 * 
 *      Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 
 *      associated documentation files (the “Software”), to deal with the Software without restriction, 
 *      including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 *      and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 *      subject to the following conditions:
 *
 *      Redistributions of source code must retain the above copyright notice, this list of conditions and the
 *      following disclaimers.
 *      
 *      Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
 *      the following disclaimers in the documentation and/or other materials provided with the distribution.
 *
 *      Neither the names of Information Trust institute, Coordinated Science Laboratory, University of Illinois,
 *      The Board of Trustees of the University of Illinois nor the names of its contributors may be used to 
 *      endorse or promote products derived from this Software without specific prior written permission.
 *      
 *      THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
 *      LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN
 *      NO EVENT SHALL THE CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 
 *      SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE SOFTWARE.
 *      
 \*****************************************************************************/
extern alias Atomic;
using System;
using System.Collections.Generic;
using System.Linq;
using Disruptor;
using System.Collections.Concurrent;
using Atomic.System.Threading;

namespace SIEGate.Core.MessageBusses
{
    class ChannelMessageBusProducer
    {
        private class Refcount
        {
            private Volatile.Integer m_count;
            public Refcount()
            {
                m_count = new Volatile.Integer(0);
            }
            public void Increment()
            {
                m_count.AtomicIncrementAndGet();
            }
            public void Decrement()
            {
                m_count.AtomicDecrementAndGet();
                if (m_count.ReadUnfenced() < 0)
                {
                    throw new IndexOutOfRangeException("Cant decrement a refcount below 0!");
                }
            }
            public bool IsZero
            {
                get
                {
                    return m_count.ReadUnfenced() <= 0;
                }
            }
        }

        public event EventHandler<MessageBusSubscriptionEventArgs> StartSubscription;
        public event EventHandler<MessageBusSubscriptionEventArgs> EndSubscription;
        public event EventHandler<PublishedEventArgs> NewPubs;

        internal RingBuffer<Routable> RingBuff;
        private QOSParams qos;
        private List<ChannelMessageBusConsumer> m_consumers = new List<ChannelMessageBusConsumer>();
        private ConcurrentDictionary<Guid, Refcount> m_streamRefs = new ConcurrentDictionary<Guid, Refcount>();
        internal ISet<Guid> ConsumedStreamIDs
        {
            get
            {
                HashSet<Guid> res = new HashSet<Guid>();
                foreach (KeyValuePair<Guid, Refcount> pair in m_streamRefs)
                {
                    if (pair.Value.IsZero)
                    {
                        res.Add(pair.Key);
                    }
                }
                return res;
            }
        }
        public ISet<Guid> StreamIDs
        {
            get
            {
                return new HashSet<Guid>(m_streamRefs.Keys);
            }
        }

        public void Send(params IMeasurement[] measurements)
        {
            /*if (m_consumers.Count() == 0)
            {
                return;
            }*/
            switch (measurements.Length)
            {
                case 0:
                    return;
                case 1:
                    {
                        Refcount count;
                        if (m_streamRefs.TryGetValue(measurements[0].StreamID, out count) && !count.IsZero)
                        {
                            long seq = RingBuff.Next();
                            RingBuff[seq].SetVals(measurements[0], qos);
                            RingBuff.Publish(seq);
                        }
                        return;
                    }
                default:
                    {
                        List<IMeasurement> toSend = measurements.Where(m =>
                        {
                            Refcount count;
                            return m_streamRefs.TryGetValue(m.StreamID, out count) && !count.IsZero;
                        }).ToList<IMeasurement>();
                        BatchDescriptor bd = RingBuff.Next(RingBuff.NewBatchDescriptor(toSend.Count()));
                        long i = 0;
                        foreach (IMeasurement m in toSend)
                        {
                            RingBuff[bd.Start + i].SetVals(measurements[i], qos);
                            i++;
                        }
                        RingBuff.Publish(bd);
                    }
                    break;
            }
        }

        private void SetGates()
        {
            RingBuff.SetGatingSequences(m_consumers.Select(c => c.Sequence).ToArray());
        }

        internal void AddConsumer(ChannelMessageBusConsumer toAdd)
        {
            m_consumers.Add(toAdd);
            foreach (Guid sid in toAdd.SubscribedStreamIDs)
            {
                AddReference(sid);
            }
            SetGates();
        }

        internal void RemoveConsumer(ChannelMessageBusConsumer toRem)
        {
            foreach (Guid sid in toRem.SubscribedStreamIDs)
            {
                RemoveReference(sid);
            }
            m_consumers.Remove(toRem);

            SetGates();
        }

        public void Publish(ISet<Guid> toAdd)
        {
            foreach (Guid signal in toAdd)
            {
              
                while (!m_streamRefs.TryAdd(signal, new Refcount()))
                {
                    if (m_streamRefs.ContainsKey(signal))
                    {
                        break;
                    }
                }
            }
            if ((object)NewPubs != null)
            {
                NewPubs(this, new PublishedEventArgs(toAdd));
            }
        }

        public void AddReference(Guid signal)
        {
            Refcount count;
            if (m_streamRefs.TryGetValue(signal, out count))
            {
                if (count.IsZero)
                    OnStartSubscription(signal);
                count.Increment();
            }
            else
            {
                throw new InvalidOperationException("Why do you subscribe to something I don't have?");
            }
        }

        public void RemoveReference(Guid signal)
        {
            Refcount count;
            if (m_streamRefs.TryGetValue(signal, out count))
            {
                count.Decrement();
                if (count.IsZero)
                    OnEndSubscription(signal);

            }
        }
        public ChannelMessageBusProducer(ISet<Guid> streamIDs, QOSParams qos, RingBuffer<Routable> rb)
        {
            RingBuff = rb;
            this.qos = qos;
            //Publish(streamIDs);
            SetGates();
        }

        // TODO: make sure this mechanism is reentrant (and adaptercontainers too)
        private void OnStartSubscription(Guid signal)
        {
            if (StartSubscription != null)
            {
                StartSubscription(this, new MessageBusSubscriptionEventArgs(signal));
            }
        }

        private void OnEndSubscription(Guid signal)
        {
            if (EndSubscription != null)
            {
                EndSubscription(this, new MessageBusSubscriptionEventArgs(signal));
            }
        }
    }
}
